import * as step from '@flow-step/step-toolkit'
import * as fs from 'fs'
import * as glob from 'glob';
import * as path from 'path'
import {RemoteStorage} from "@flow-step/step-toolkit/src/oss";
import process from 'process'
import {installOssutil} from './install'
import axios from 'axios'

export async function getOssEndpoint() {
    let endpoint = `https://oss-${process.env.OSS_REGION as string}-internal.aliyuncs.com`
    try {
        await axios.get(`${endpoint}`, {timeout: 1000})
    } catch (error) {
        // @ts-ignore
        if (!(error.status && error.status == 403)) {
            step.info('OSS internal network address is unreachable, using OSS public network address instead. ')
            endpoint = `https://oss-${process.env.OSS_REGION as string}.aliyuncs.com`
        }

    }
    return endpoint
}

export async function upload() {

    const sourceFilePath = process.env.UPLOAD_PATH as string
    const projectDir = process.env.PROJECT_DIR as string
    const localPath = path.join(projectDir, sourceFilePath)
    let ossPath = process.env.OSS_PATH || ""
    if (ossPath!="" && !ossPath.endsWith('/')){
        ossPath = ossPath + '/'
    }

    if (!fs.existsSync(localPath)) {
        throw new Error(`sourceFilePath ${localPath} does not exist`)
    }
    if (!fs.statSync(localPath).isFile()){
        let files = await glob.glob('**/*',{ cwd: localPath, dot: true, absolute:true})
        if (files.length == 0){
            step.warning("all files already uploaded or user dir is empty, skip uploading")
            return
        }
    }
    // get oss certificate
    const flowJobToken: string | undefined =
        process.env['FLOW_JOB_TOKEN']
    if (flowJobToken == undefined) {
        throw new Error('missing FLOW_JOB_TOKEN')
    }
    const OssRamCertificate = await step.certificate.getOssRamServiceConnectionCertificate(flowJobToken,process.env.SERVICE_CONNECTION_ID as string)
    if (OssRamCertificate == undefined || OssRamCertificate.accessKeyId == undefined) {
        throw new Error(`got OssRamServiceConnectionCertificate but error: ${JSON.stringify(OssRamCertificate)}`)
    }
    let endpoint = await getOssEndpoint()
    const accessKeyID = OssRamCertificate.accessKeyId;
    const accessKeySecret = OssRamCertificate.accessKeySecret;
    const securityToken = OssRamCertificate.securityToken;
    const bucket = process.env.OSS_BUCKET as string;

    const remoteStorage = new step.oss.OSSStorage({
        endpoint: endpoint,
        accessKeyID: accessKeyID,
        accessKeySecret: accessKeySecret,
        securityToken: securityToken,
        bucket: bucket,
    })

    if (process.env.METAS && process.env.METAS != "[]") {
        step.info("Found meta rules. upload specified files");
        let metas = JSON.parse(process.env.METAS);
        for (let meta of metas) {
            await uploadWithMeta(remoteStorage,localPath,ossPath, meta.key, meta.value);
        }
    }

    step.info("uploading files without meta info")
    //ensure dirs are also uploaded
    step.info(`ossutil cp -r ${localPath} oss://${bucket}/${ossPath} --update -e ${endpoint}`)
    const resultCode = await step.exec.call('ossutil', ['cp','-r', localPath, `oss://${bucket}/${ossPath}`, '--update', '-i', accessKeyID, '-k', accessKeySecret, '-t', securityToken, '-e', endpoint ],{
        listener: {
            stdoutLine: (line: string) => step.info(line),
            stderrLine: (line: string) => step.error(line)
        }
    })
    if (resultCode != 0) {
        throw new Error(`Failed to upload files to oss, exit code ${resultCode}`);
    }

    step.success("upload success")
    const OSS_BUCKET_URL = `https://oss.console.aliyun.com/bucket/oss-${process.env.OSS_REGION as string}/${process.env.OSS_BUCKET as string}/object`
    step.success(`OSS_BUCKET_URL=${OSS_BUCKET_URL}`)

    if (process.env.PIPELINE_LOCALE == 'zh_CN'){
        step.interaction.addLink('上传地址', OSS_BUCKET_URL)
    }else{
        step.interaction.addLink('Upload Address', OSS_BUCKET_URL)
    }
}


export async function uploadWithMeta(client: RemoteStorage,localPath:string,ossPath:string,suffix:string,meta:string) {

    step.info(`upload files suffixed with ${suffix} and add meta info ${meta}`)
    let files: string[]
    const pathIsFile = fs.statSync(localPath).isFile()
    if (pathIsFile){
        files = [localPath]
    }else {
        files = await glob.glob(`**/*.${suffix}`, {cwd: localPath, nodir: true, dot: true})
        if (files.length == 0){
            step.warning(`Could not find files suffixed with ${suffix}, skip`)
            return
        }
    }

    type Headers = {
        [key: string]: string;
    };
    const headers: Headers = {};
    const metaParts = meta.split('#');
    metaParts.forEach(part => {
        const [key, value] = part.split(':');
        headers[key.trim()] = value.trim();
    });

    try {
        for (let file of files) {
            if (pathIsFile){
                let objectKey = path.join(ossPath,path.basename(file));
                await step.oss.uploadWithUpdate(client, file, objectKey, headers)
            }else {
                let objectKey = path.join(ossPath,file);
                let localPathFile = path.join(localPath, file);
                await step.oss.uploadWithUpdate(client, localPathFile, objectKey, headers)
            }
        }
        step.info(`successfully upload files suffixed with ${suffix} and add meta info ${meta}`)
    }catch (err){
        step.error(`failed to upload files suffixed with ${suffix} and add meta info ${meta}`)
        throw err;
    }
}
